package biolabs.mcl

import biolabs.core.Application
import biolabs.mcl.syntax.MultiTypeAST

import scala.collection.{Map => AMap}

object MultiType {
	def analyze(ast: MultiTypeAST, model: ModelDefinition, resolvedTypeVars: AMap[String, TypeVar], app: Application): AnalysisResult[MultiType] = {
		val (suc, err) = ast.types map { SingleType.analyze(_, model, resolvedTypeVars, app) } partition { _.successful }
		if(!err.isEmpty)
			AnalysisError(err map { _.getErrorMessage() } mkString "\n")
		else
			AnalysisSuccess(new MultiType(suc map { _.getResult() }))
	}

	def apply(t: SingleType) = new MultiType(List(t))
}

/** Represents a type composed of one or more Java types.
  * It is used for representing Scala types, which can be
  * composed of multiple simple types using the `with` keyword.
  */
class MultiType(val types: List[SingleType]) {

	if(types.length == 0)
		throw new IllegalArgumentException("Empty list of types")

	def asSingleType(): SingleType = {
		if(types.length > 1)
			throw new IllegalStateException(this + " cannot be treated as SingleType");
		types.head
	}

	def isDescendantOf(ancestor: Class[_]) =
		types exists { _.isDescendantOf(ancestor) }

	def isAssignableFrom(sub: MultiType) =
		types.forall(_.isAssignableFrom(sub))

	def isAssignableFrom(sub: SingleType) =
		types.forall(_.isAssignableFrom(sub))

	def getTypeArgumentsOf(ancestor: Class[_]) =
		types collectFirst({ case t if t.isDescendantOf(ancestor) => t.getTypeArgumentsOf(ancestor) }) match {
			case Some(args) => args
			case None => throw new IllegalArgumentException(this + " is not a descendant of " + ancestor)
		}

	def substituteTypeVariables(substitutions: Map[String, MultiType]): MultiType =
		new MultiType(types flatMap {
			case at: ActualType => List(at.substituteTypeVariables(substitutions))
			case tv: TypeVar => substitutions.get(tv.name) match {
				case Some(mt) => mt.types
				case None => List(tv)
			}
			case st: SelfRef => List(st.substituteTypeVariables(substitutions))
		})

	/** Delegate candidates of a `MultiType` is a union of delegate
	  * candidates of individual `SingleType`s.
	  */
	def getDelegateCandidates(interaction: ActualType): List[DelegateMethod] =
		types flatMap { _.getDelegateCandidates(interaction) }

	/** Returns the single delegate candidate for the given interaction.
	  * @throws SemanticException if there is no or more than one delegate
	  * candidate for the given interaction.
	  */
	def getDelegateMethod(interaction: ActualType): DelegateMethod =
		getDelegateCandidates(interaction) match {
			case Nil =>
				throw new SemanticException(ScalaCodeGen.refer(this) + " has no delegate for " + ScalaCodeGen.refer(interaction))
			case method::Nil =>
				method
			case methods =>
				throw new SemanticException("In type " + ScalaCodeGen.refer(this) +
					": Ambiguity of delegate method for " + ScalaCodeGen.refer(interaction) +
					". Candidates are: " + methods.map{_.method}.mkString(", "))
		}

	override def hashCode(): Int = {
		types map {_.hashCode} reduce ((a, b) => a*31 + b)
	}

	override def equals(other: Any): Boolean = other match {
		case that: MultiType => isEqualTo(that, Nil)
		case _ => false
	}

	def isEqualTo(that: MultiType, comparing: List[(TypeVar, TypeVar)]) =
		types.corresponds(that.types)((a, b) => a.isEqualTo(b, comparing))
}